// 2020/8/26: 首个版本（根据EnvSampleV2.cs 2020/1/14版本修改）
// 2020/11/3: 新增气压、相对湿度、能见度、风向、风速、PM10、道路状况、车道交通状况、灾害情况等字段。道路交通状况更名
// 2022/10/18: 增加SO标签
// 2022/11/3: 使用SetTime复制全部时间信息
// 2023/8/17: 新增瞬时雨量字段
// 2023/10/10: 补充部分英文注释
// 2023/10/24: 新增字段（累计雨量、精确风向、总辐射强度、紫外线辐射强度、臭氧浓度、一氧化碳浓度、二氧化硫浓度、二氧化氮浓度）
// 2025/3/28: 实现ToGeneralSample和Interpolate
// 2025/7/21: 修正SpeedLimit插值

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using ASEva;
using SO = System.Reflection.ObfuscationAttribute;

namespace EnvSampleV3
{
    [SO]
    enum WeatherType
    {
        Unknown = 0, // 未知
        Sunny = 1, // 晴
        Cloudy = 2, // 阴
        Rainy = 3, // 雨
        Snowy = 4, // 雪
        Foggy = 5, // 雾
        Sand = 6, // 沙尘
    };

    [SO]
    enum RoadType
    {
        Unknown = 0, // 未知
        Highway = 1, // Without traffic light / 高速路，无红绿灯
        CityExpress = 2, // Without traffic light / 城市快速路，无红绿灯
        CityMain = 3, // With traffic light / 城市主干道，有红绿灯
        GeneralRoad = 4, // With traffic light / 一般道路，有红绿灯
        CountryRoad = 5, // Without traffic light / 乡村道路，无红绿灯
    };

    [SO]
    enum SpotType
    {
        Unknown = 0,  //未知
        TollGate = 1, // 收费站
        ServiceArea = 2, // 服务区
        Tunnel = 3, // 隧道
        Accident = 4, // 事故
        Crossing = 5, // Intersection / 路口
        Roundabout = 7, // 环岛
        HighwayEntrance = 8, // 高速入口
        HighwayExit = 9, // 高速出口
        Bridge = 10, // 桥
        ExpressEntrance = 11, // 快速路入口
        ExpressExit = 12, // 快速路出口
    }

    class Spot
    {
        public SpotType Type { get; set; }  // 场所类型
        public double Distance { get; set; } // [m] Distance between the spot and subject vehicle / 本车到场所的距离
    }

    [SO]
    enum TrafficStatus
    {
        Unknown = 0, // 未知
        Open = 1, // 畅通
        Slow = 2, // 缓慢
        Crowded = 3, // 拥挤
        Reverse = 4, // Only for adjacent lane traffic / 逆向（仅限相邻车道交通状况）
    };

    [SO]
    enum WindDirection
    {
        Unknown = 0, // 未知
        East = 1, // 东风
        South = 2, // 南风
        West = 3, // 西风
        North = 4, // 北风
        SouthEast = 5, // 东南风
        NorthEast = 6, // 东北风
        SouthWest = 7, // 西南风
        NorthWest = 8, // 西北风
    };

    [SO]
    enum RoadSurfaceStatus
    {
        Unknown = 0, // 未知
        Dry = 1, // 干燥
        Wet = 2, // 湿滑
        SnowCovered = 3, // 积雪
        Frozen = 4, // 结冰
    };

    [SO]
    enum DisasterStatus
    {
        Unknown = 0, // 未知
        No = 1, // 无灾害
        Yes = 2, // 有灾害
    };

    class EnvSample : Sample
    {
        public WeatherType WeatherType { get; set; }   //天气类型
        public double? Temperature { get; set; } // [°C] 温度
        public double? Humidity { get; set; } // [%] Relative humidity / 相对湿度
        public double? Pressure { get; set; } // [mb] Atmospheric pressure / 气压
        public double? PM25 { get; set; } // [ug/m3] PM 2.5
        public double? PM10 { get; set; } // [ug/m3] PM 10
        public double? O3Concentration { get; set; } // [ppb] Ozone concentration / 臭氧浓度
        public double? COConcentration { get; set; } // [ppb] Carbon monoxide concentration / 一氧化碳浓度
        public double? SO2Concentration { get; set; } // [ppb] Sulfur dioxide concentration / 二氧化硫浓度
        public double? NO2Concentration { get; set; } // [ppb] Nitrogen dioxide concentration / 二氧化氮浓度
        public double? Visibility { get; set; } // [KM] 能见度
        public double? Illumination { get; set; } // [lux] 光照强度
        public double? TotalRadiationIntensity { get; set; } // [W/m2] 总辐射强度
        public double? UltravioletRadiationIntensity { get; set; } // [W/m2] 紫外线辐射强度
        public double? Rainfall { get; set; } // [mm/min] Instantaneous rainfall / 瞬时雨量
        public double? CumulativeRainfall { get; set; } // [mm] 累计雨量
        public double? WindSpeed { get; set; }  // [m/s] 风速
        public double? AccurateWindDirection { get; set; } // [deg] Accurate wind direction (from), 0 is north wind, CCW is positive, ranges -180~180 / 北风为0，CCW为正，范围在-180～180度
        public WindDirection WindDirection { get; set; } // Coarse wind direction (from) / 大致风向
        public DisasterStatus TyphoonStatus { get; set; } // 台风状况
        public DisasterStatus TornadoStatus { get; set; } // 龙卷风状况
        public DisasterStatus LightningStatus { get; set; } // 雷电状况
        public DisasterStatus HailStatus { get; set; } // 冰雹状况
        public String RoadName { get; set; } // 道路名称
        public int? SpeedLimit { get; set; } // [KPH] 道路限速
        public RoadType RoadType { get; set; } // 道路类型
        public RoadSurfaceStatus RoadSurfaceStatus { get; set; } // 路面状况
        public TrafficStatus RoadTraffic { get; set; } // 道路交通状况
        public TrafficStatus CurrentLaneTraffic { get; set; } // 本车道交通状况
        public TrafficStatus LeftLaneTraffic { get; set; } // 左车道交通状况
        public TrafficStatus RightLaneTraffic { get; set; } // 右车道交通状况
        public List<Spot> Spots { get; set; } // 场所列表

        public EnvSample()
        {
            Spots = new List<Spot>();
        }

        public static string Title
        {
            get
            {
                return "Road name size,Spot count,Weather,Temperature[°C],PM2.5[ug/m3],Speed limit[KPH],Road type,Illumination[lux],Wind speed[m/s],Road traffic status,Road name,Pressure[mb],Humidity[%],Visibility[KM],Coarse wind direction,PM10[ug/m3],Road surface status,Current lane traffic status,Left lane traffic status,Right lane traffic status,Typhoon status,Tornado status,Lightning status,Hail status,Instantaneous rainfall[mm/min],Cumulative rainfall[mm],Accurate wind direction[deg],Total radiation intensity[W/m2],Ultraviolet radiation intensity[W/m2],Ozone concentration[ppb],Carbon monoxide concentration[ppb],Sulfur dioxide concentration[ppb],Nitrogen dioxide concentration[ppb],...";
            }
        }

        public static string Protocol
        {
            get
            {
                return "env-sample-v3";
            }
        }

        public static string[] Protocols
        {
            get
            {
                return new string[] { "env-sample-v1", "env-sample-v2", "env-sample-v3" };
            }
        }

        public override string GetGeneralSampleProtocol()
        {
            return Protocol;
        }

        public override string[] GetGeneralSampleProtocols()
        {
            return Protocols;
        }

        public override bool FromGeneralSample(GeneralSample sample)
        {
            SetTime(sample);

            if (sample.Protocol == "env-sample-v1")
            {
                if (sample.Values.Count < 1) return false;

                int roadNameSize = (int)sample.Values[0].number;
                if (sample.Values.Count != 13 + roadNameSize) return false;

                WeatherType = sample.Values[1].IsNumber() ? (WeatherType)(int)sample.Values[1].number : WeatherType.Unknown;
                Temperature = sample.Values[2].ToDouble();
                PM25 = sample.Values[3].ToDouble();
                SpeedLimit = sample.Values[4].IsNumber() ? (int?)(int)sample.Values[4].number : null;
                RoadType = sample.Values[5].IsNumber() ? (RoadType)(int)sample.Values[5].number : RoadType.Unknown;
                Illumination = sample.Values[6].ToDouble();
                WindSpeed = sample.Values[7].ToDouble();
                RoadTraffic = sample.Values[12].IsNumber() ? (TrafficStatus)(int)sample.Values[12].number : TrafficStatus.Unknown;

                var roadNameBin = new byte[roadNameSize];
                for (int i = 0; i < roadNameSize; i++)
                {
                    roadNameBin[i] = (byte)sample.Values[13 + i].ToInt().Value;
                }
                RoadName = Encoding.UTF8.GetString(roadNameBin);

                return true;
            }
            else if (sample.Protocol == "env-sample-v2")
            {
                if (sample.Values.Count < 2) return false;

                int roadNameSize = (int)sample.Values[0].number;
                int spotCount = (int)sample.Values[1].number;

                if (sample.Values.Count != 10 + 2 * spotCount + roadNameSize) return false;

                WeatherType = sample.Values[2].IsNumber() ? (WeatherType)(int)sample.Values[2].number : WeatherType.Unknown;
                Temperature = sample.Values[3].ToDouble();
                PM25 = sample.Values[4].ToDouble();
                SpeedLimit = sample.Values[5].IsNumber() ? (int?)(int)sample.Values[5].number : null;
                RoadType = sample.Values[6].IsNumber() ? (RoadType)(int)sample.Values[6].number : RoadType.Unknown;
                Illumination = sample.Values[7].ToDouble();
                WindSpeed = sample.Values[8].ToDouble();
                RoadTraffic = sample.Values[9].IsNumber() ? (TrafficStatus)(int)sample.Values[9].number : TrafficStatus.Unknown;

                Spots = new List<Spot>();
                for (int i = 0; i < spotCount; i++)
                {
                    var spotType = sample.Values[10 + 2 * i].ToInt();
                    var spotDistance = sample.Values[10 + 2 * i + 1].ToDouble();
                    if (spotType != null && spotDistance != null)
                    {
                        Spots.Add(new Spot()
                            {
                                Type = (SpotType)spotType.Value,
                                Distance = spotDistance.Value,
                            });
                    }
                }

                var roadNameBin = new byte[roadNameSize];
                for (int i = 0; i < roadNameSize; i++)
                {
                    roadNameBin[i] = (byte)sample.Values[10 + 2 * spotCount + i].ToInt().Value;
                }
                RoadName = Encoding.UTF8.GetString(roadNameBin);

                return true;
            }
            else if (sample.Protocol == "env-sample-v3")
            {
                if (sample.Values.Count < 2) return false;

                int roadNameSize = (int)sample.Values[0].number;
                int spotCount = (int)sample.Values[1].number;

                if (sample.Values.Count != 64 + 2 * spotCount + roadNameSize) return false;

                WeatherType = sample.Values[2].IsNumber() ? (WeatherType)(int)sample.Values[2].number : WeatherType.Unknown;
                Temperature = sample.Values[3].ToDouble();
                PM25 = sample.Values[4].ToDouble();
                SpeedLimit = sample.Values[5].IsNumber() ? (int?)(int)sample.Values[5].number : null;
                RoadType = sample.Values[6].IsNumber() ? (RoadType)(int)sample.Values[6].number : RoadType.Unknown;
                Illumination = sample.Values[7].ToDouble();
                WindSpeed = sample.Values[8].ToDouble();
                RoadTraffic = sample.Values[9].IsNumber() ? (TrafficStatus)(int)sample.Values[9].number : TrafficStatus.Unknown;
                Pressure = sample.Values[11].ToDouble();
                Humidity = sample.Values[12].ToDouble();
                Visibility = sample.Values[13].ToDouble();
                WindDirection = sample.Values[14].IsNumber() ? (WindDirection)sample.Values[14].number : WindDirection.Unknown;
                PM10 = sample.Values[15].ToDouble();
                RoadSurfaceStatus = sample.Values[16].IsNumber() ? (RoadSurfaceStatus)sample.Values[16].number : RoadSurfaceStatus.Unknown;
                CurrentLaneTraffic = sample.Values[17].IsNumber() ? (TrafficStatus)sample.Values[17].number : TrafficStatus.Unknown;
                LeftLaneTraffic = sample.Values[18].IsNumber() ? (TrafficStatus)sample.Values[18].number : TrafficStatus.Unknown;
                RightLaneTraffic = sample.Values[19].IsNumber() ? (TrafficStatus)sample.Values[19].number : TrafficStatus.Unknown;
                TyphoonStatus = sample.Values[20].IsNumber() ? (DisasterStatus)sample.Values[20].number : DisasterStatus.Unknown;
                TornadoStatus = sample.Values[21].IsNumber() ? (DisasterStatus)sample.Values[21].number : DisasterStatus.Unknown;
                LightningStatus = sample.Values[22].IsNumber() ? (DisasterStatus)sample.Values[22].number : DisasterStatus.Unknown;
                HailStatus = sample.Values[23].IsNumber() ? (DisasterStatus)sample.Values[23].number : DisasterStatus.Unknown;
                Rainfall = sample.Values[24].ToDouble();
                CumulativeRainfall = sample.Values[25].ToDouble();
                AccurateWindDirection = sample.Values[26].ToDouble();
                TotalRadiationIntensity = sample.Values[27].ToDouble();
                UltravioletRadiationIntensity = sample.Values[28].ToDouble();
                O3Concentration = sample.Values[29].ToDouble();
                COConcentration = sample.Values[30].ToDouble();
                SO2Concentration = sample.Values[31].ToDouble();
                NO2Concentration = sample.Values[32].ToDouble();

                Spots = new List<Spot>();
                for (int i = 0; i < spotCount; i++)
                {
                    var spotType = sample.Values[64 + 2 * i].ToInt();
                    var spotDistance = sample.Values[64 + 2 * i + 1].ToDouble();
                    if (spotType != null && spotDistance != null)
                    {
                        Spots.Add(new Spot()
                        {
                            Type = (SpotType)spotType.Value,
                            Distance = spotDistance.Value,
                        });
                    }
                }

                if (sample.Values[10].mode == GeneralSampleValueMode.Text)
                {
                    RoadName = sample.Values[10].text;
                }
                else
                {
                    var roadNameBin = new byte[roadNameSize];
                    for (int i = 0; i < roadNameSize; i++)
                    {
                        roadNameBin[i] = (byte)sample.Values[64 + 2 * spotCount + i].ToInt().Value;
                    }
                    RoadName = Encoding.UTF8.GetString(roadNameBin);
                }

                return true;
            }

            return false;
        }

        public override GeneralSample ToGeneralSample()
        {
            var sample = new GeneralSample();
            sample.SetTime(this);
            sample.Protocol = Protocol;

            var spotCount = Spots == null ? 0 : Spots.Count;
            var data = new GeneralSampleValue[64 + 2 * spotCount];

            data[1] = new GeneralSampleValue((double)spotCount);
            data[2] = new GeneralSampleValue((double)(int)WeatherType);
            if (Temperature != null) data[3] = new GeneralSampleValue(Temperature.Value);
            if (PM25 != null) data[4] = new GeneralSampleValue(PM25.Value);
            if (SpeedLimit != null) data[5] = new GeneralSampleValue((double)SpeedLimit.Value);
            data[6] = new GeneralSampleValue((double)(int)RoadType);
            if (Illumination != null) data[7] = new GeneralSampleValue(Illumination.Value);
            if (WindSpeed != null) data[8] = new GeneralSampleValue(WindSpeed.Value);
            data[9] = new GeneralSampleValue((double)(int)RoadTraffic);
            if (RoadName != null) data[10] = new GeneralSampleValue(RoadName);
            if (Pressure != null) data[11] = new GeneralSampleValue(Pressure.Value);
            if (Humidity != null) data[12] = new GeneralSampleValue(Humidity.Value);
            if (Visibility != null) data[13] = new GeneralSampleValue(Visibility.Value);
            data[14] = new GeneralSampleValue((double)(int)WindDirection);
            if (PM10 != null) data[15] = new GeneralSampleValue(PM10.Value);
            data[16] = new GeneralSampleValue((double)(int)RoadSurfaceStatus);
            data[17] = new GeneralSampleValue((double)(int)CurrentLaneTraffic);
            data[18] = new GeneralSampleValue((double)(int)LeftLaneTraffic);
            data[19] = new GeneralSampleValue((double)(int)RightLaneTraffic);
            data[20] = new GeneralSampleValue((double)(int)TyphoonStatus);
            data[21] = new GeneralSampleValue((double)(int)TornadoStatus);
            data[22] = new GeneralSampleValue((double)(int)LightningStatus);
            data[23] = new GeneralSampleValue((double)(int)HailStatus);
            if (Rainfall != null) data[24] = new GeneralSampleValue(Rainfall.Value);
            if (CumulativeRainfall != null) data[25] = new GeneralSampleValue(CumulativeRainfall.Value);
            if (AccurateWindDirection != null) data[26] = new GeneralSampleValue(AccurateWindDirection.Value);
            if (TotalRadiationIntensity != null) data[27] = new GeneralSampleValue(TotalRadiationIntensity.Value);
            if (UltravioletRadiationIntensity != null) data[28] = new GeneralSampleValue(UltravioletRadiationIntensity.Value);
            if (O3Concentration != null) data[29] = new GeneralSampleValue(O3Concentration.Value);
            if (COConcentration != null) data[30] = new GeneralSampleValue(COConcentration.Value);
            if (SO2Concentration != null) data[31] = new GeneralSampleValue(SO2Concentration.Value);
            if (NO2Concentration != null) data[32] = new GeneralSampleValue(NO2Concentration.Value);

            if (Spots != null)
            {
                for (int i = 0; i < spotCount; i++)
                {
                    data[64 + 2 * i] = new GeneralSampleValue((double)(int)Spots[i].Type);
                    data[64 + 2 * i + 1] = new GeneralSampleValue(Spots[i].Distance);
                }
            }

            sample.Values = data.ToList();
            sample.NumberOfSignificants = data.Length;

            return sample;
        }

        public override bool SupportInterpolation()
        {
            return true;
        }

        protected override Sample Interpolate(Sample.SearchResult input)
        {
            var s1 = input.s1 as EnvSample;
            var s2 = input.s2 as EnvSample;
            var w1 = input.w1;
            var w2 = input.w2;

            var output = new EnvSample();

            output.WeatherType = w1 > w2 ? s1.WeatherType : s2.WeatherType;
            output.Temperature = s1.Temperature * w1 + s2.Temperature * w2;
            output.Humidity = s1.Humidity * w1 + s2.Humidity * w2;
            output.Pressure = s1.Pressure * w1 + s2.Pressure * w2;
            output.PM25 = s1.PM25 * w1 + s2.PM25 * w2;
            output.PM10 = s1.PM10 * w1 + s2.PM10 * w2;
            output.O3Concentration = s1.O3Concentration * w1 + s2.O3Concentration * w2;
            output.COConcentration = s1.COConcentration * w1 + s2.COConcentration * w2;
            output.SO2Concentration = s1.SO2Concentration * w1 + s2.SO2Concentration * w2;
            output.NO2Concentration = s1.NO2Concentration * w1 + s2.NO2Concentration * w2;
            output.Visibility = s1.Visibility * w1 + s2.Visibility * w2;
            output.Illumination = s1.Illumination * w1 + s2.Illumination * w2;
            output.TotalRadiationIntensity = s1.TotalRadiationIntensity * w1 + s2.TotalRadiationIntensity * w2;
            output.UltravioletRadiationIntensity = s1.UltravioletRadiationIntensity * w1 + s2.UltravioletRadiationIntensity * w2;
            output.Rainfall = s1.Rainfall * w1 + s2.Rainfall * w2;
            output.CumulativeRainfall = s1.CumulativeRainfall * w1 + s2.CumulativeRainfall * w2;
            output.WindSpeed = s1.WindSpeed * w1 + s2.WindSpeed * w2;
            output.AccurateWindDirection = s1.AccurateWindDirection * w1 + s2.AccurateWindDirection * w2;
            output.WindDirection = w1 > w2 ? s1.WindDirection : s2.WindDirection;
            output.TyphoonStatus = w1 > w2 ? s1.TyphoonStatus : s2.TyphoonStatus;
            output.TornadoStatus = w1 > w2 ? s1.TornadoStatus : s2.TornadoStatus;
            output.LightningStatus = w1 > w2 ? s1.LightningStatus : s2.LightningStatus;
            output.HailStatus = w1 > w2 ? s1.HailStatus : s2.HailStatus;
            output.RoadName = w1 > w2 ? s1.RoadName : s2.RoadName;
            output.SpeedLimit = w1 > w2 ? s1.SpeedLimit : s2.SpeedLimit;
            output.RoadType = w1 > w2 ? s1.RoadType : s2.RoadType;
            output.RoadSurfaceStatus = w1 > w2 ? s1.RoadSurfaceStatus : s2.RoadSurfaceStatus;
            output.RoadTraffic = w1 > w2 ? s1.RoadTraffic : s2.RoadTraffic;
            output.CurrentLaneTraffic = w1 > w2 ? s1.CurrentLaneTraffic : s2.CurrentLaneTraffic;
            output.LeftLaneTraffic = w1 > w2 ? s1.LeftLaneTraffic : s2.LeftLaneTraffic;
            output.RightLaneTraffic = w1 > w2 ? s1.RightLaneTraffic : s2.RightLaneTraffic;
            output.Spots = w1 > w2 ? s1.Spots : s2.Spots;

            return output;
        }
    }
}
